Namespace ShapeLibrary.Design
    '''
    '''     The LineDesigner is the designer for shape objects
    '''     that derive from Line.
    '''
    Friend Class LineDesigner
        Inherits ShapeDesigner
        ' Objects we use to identify hit test locations. A line
        ' can be manipulated by moving it, or by adjusting the
        ' start and end points.  All shapes can be moved so the
        ' HitMove object is defined in ShapeDesigner.
        '
        Private Shared ReadOnly HitStart As Object = New Object()
        Private Shared ReadOnly HitEnd As Object = New Object()

        '''
        '''     Implementation of the abstract property BoundingBox
        '''     from ShapeDesigner.
        '''
        Protected Overrides Property BoundingBox() As Rectangle
            Get
                Dim bounds As Rectangle = New Rectangle()
                bounds.X = Math.Min(Line.StartPoint.X, Line.EndPoint.X)
                bounds.Y = Math.Min(Line.StartPoint.Y, Line.EndPoint.Y)
                bounds.Width = Math.Abs(Line.EndPoint.X - Line.StartPoint.X)
                bounds.Height = Math.Abs(Line.EndPoint.Y - Line.StartPoint.Y)
                Return bounds
            End Get
            Set(ByVal Value As Rectangle)
                Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("StartPoint")
                If Not (prop Is Nothing) Then
                    prop.SetValue(Component, New Point(Value.X, Value.Y))
                End If

                prop = TypeDescriptor.GetProperties(Component)("EndPoint")
                If Not (prop Is Nothing) Then
                    prop.SetValue(Component, New Point(Value.X + Value.Width, Value.Y + Value.Height))
                End If
            End Set
        End Property

        '''
        '''     This is a helper property that returns the line we are currently designing.
        '''     The default implementation just casts Component to a line.
        '''
        Protected Overridable ReadOnly Property Line() As Line
            Get
                Return CType(Component, Line)
            End Get
        End Property

        '''
        '''     Implementation for the abstract method GetHitTest
        '''     from ShapeDesigner.  We support three possible 
        '''     hit test locations for a line:  one on each end and
        '''     anywhere in the middle.
        '''
        Friend Overrides Function GetHitTest(ByVal x As Integer, ByVal y As Integer) As Object
            Dim p1 As PointF = Line.StartPoint
            Dim p2 As PointF = Line.EndPoint

            ' Is the point within the bounding rectangle for this line?
            '
            Dim bounds As Rectangle = BoundingBox
            bounds.Inflate(AdornmentDimensions)

            If Not bounds.Contains(x, y) Then
                Return Nothing
            End If

            ' Calcuate the equation for our line and see if the point is on it.
            ' We use the adornment dimensions to provide a slop factor so the
            ' cursor does not have to be exactly on the line.
            '
            If p2.X = p1.X Then
                ' Vertical line has infinite slope, so we can't use geometry.
                '
                If Math.Abs(x - p2.X) > AdornmentDimensions.Width Then
                    Return Nothing

                End If
            Else
                Dim yslope As Decimal = p2.Y - p1.Y
                Dim xslope As Decimal = p2.X - p1.X
                Dim slope As Decimal = yslope / xslope
                Dim b As Integer = CType(p1.Y - slope * p1.X, Integer)

                If CType(Math.Abs((slope * x + b) - y), Integer) > AdornmentDimensions.Width Then
                    Return Nothing
                End If
            End If

            ' We are on the line.  We must return one of three hit test
            ' objects for this:  A start point, and end point, and
            ' a middle.
            '
            Dim grabCenter As Integer = AdornmentDimensions.Width / 2
            Dim slopRect As RectangleF = New RectangleF(p1, AdornmentDimensions)
            slopRect.Offset(-grabCenter, -grabCenter)

            If slopRect.Contains(x, y) Then
                Return HitStart
            End If

            slopRect.X = p2.X - grabCenter
            slopRect.Y = p2.Y - grabCenter

            If slopRect.Contains(x, y) Then
                Return HitEnd
            End If

            Return HitMove
        End Function

        '''
        '''     Implementation for the abstract method GetCursor
        '''     from ShapeDesigner.  We return one of three cursors,
        '''     depending on the value of the hitObject passed in.
        '''     If the object is HitMove, we return the size all
        '''     cursor which is a four headed arrow (indicating a move
        '''     of the shape).  If the hit object is either HitStart
        '''     or HitEnd we calculate the slope of the line, either 
        '''     positive or negative, so we can return a move
        '''     cursor that looks right with respect to the line.
        '''
        Friend Overrides Function GetCursor(ByVal hitObject As Object) As Cursor
            If hitObject Is HitMove Then
                Return Cursors.SizeAll
            End If

            ' For the other hit tests, we need to know the slope
            ' of the line.
            '
            Dim p1 As PointF = Line.StartPoint
            Dim p2 As PointF = Line.EndPoint

            ' For an infinite slope, bail out now.
            '
            If p2.X = p1.X Then
                Return Cursors.SizeNS
            End If

            ' For zero slope, use an E-W cursor.
            '
            If p2.Y = p1.Y Then
                Return Cursors.SizeWE
            End If

            Dim yslope As Decimal = p2.Y - p1.Y
            Dim xslope As Decimal = p2.X - p1.X
            Dim slope As Decimal = yslope / xslope

            If slope > 0 Then
                Return Cursors.SizeNWSE
            Else
                Return Cursors.SizeNESW
            End If
        End Function

        '''
        '''     Implementation for the abstract method Drag
        '''     from ShapeDesigner.  Here we either move one
        '''     or both points of the line depending on what
        '''     hit object was passed.
        '''
        Friend Overrides Sub Drag(ByVal hitObject As Object, ByVal x As Integer, ByVal y As Integer)
            ' Whenever we manipulate more than one property at a time
            ' we create a transaction.  A single transaction is a single
            ' unit of work, and this is what is undone. The names for the
            ' transaction below will be displayed in the undo menu's
            ' drop down list of undoable actions.
            '
            Dim trans As DesignerTransaction = Nothing
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)

            If Not (host Is Nothing) Then
                If hitObject Is HitStart Or hitObject Is HitEnd Then
                    trans = host.CreateTransaction("Resize " + Component.Site.Name)
                Else
                    If hitObject Is HitMove Then
                        trans = host.CreateTransaction("Move " + Component.Site.Name)
                    End If
                End If

                ' There is a very subtle but planned behavior here that should be
                ' noted.  Our designer supports multiple selection.  It is impossible
                ' to select two different types of shapes and do anything other than
                ' move them, however, because they all have different rules.  So, if
                ' an ellipse was selected and resized, the hitObject passed to us won't
                ' be one we understand.  In this case trans will be null and we won't
                ' move our shape.
                '
                If Not (trans Is Nothing) Then
                    Try
                        If hitObject Is HitStart Or hitObject Is HitMove Then
                            ' Always change properties using property descriptors.  If you don't
                            ' changes may not be broadcast to everyone.
                            '
                            Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("StartPoint")
                            If Not (prop Is Nothing) Then
                                Dim newPoint As Point = CType(prop.GetValue(Component), Point)
                                newPoint.X += x
                                newPoint.Y += y
                                prop.SetValue(Component, newPoint)
                            End If
                        End If

                        If hitObject Is HitEnd Or hitObject Is HitMove Then
                            Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(Component)("EndPoint")
                            If Not (prop Is Nothing) Then
                                Dim newPoint As Point = CType(prop.GetValue(Component), Point)
                                newPoint.X += x
                                newPoint.Y += y
                                prop.SetValue(Component, newPoint)
                            End If
                        End If
                        trans.Commit()
                    Finally
                        CType(trans, IDisposable).Dispose()
                    End Try
                End If
            End If
        End Sub

        '''
        '''     Implementation for the abstract method DrawAdornments 
        '''     from ShapeDesigner.  For a line, we draw grab handles
        '''     on each end of the line.
        '''
        Friend Overrides Sub DrawAdornments(ByVal g As Graphics, ByVal primary As Boolean)
            ' The line designer draws a grab on each end of the line.
            '
            Dim grabCenter As Integer = AdornmentDimensions.Width / 2
            Dim b As New Point(Line.StartPoint.X, Line.StartPoint.Y)
            Dim c As New Point(Line.EndPoint.X, Line.EndPoint.Y)
            Dim startRect As Rectangle = New Rectangle(b, AdornmentDimensions)
            Dim endRect As Rectangle = New Rectangle(c, AdornmentDimensions)
            startRect.Offset(-grabCenter, -grabCenter)
            endRect.Offset(-grabCenter, -grabCenter)

            ControlPaint.DrawGrabHandle(g, startRect, primary, True)
            ControlPaint.DrawGrabHandle(g, endRect, primary, True)
        End Sub

        '''
        '''     Implementation for the abstract method DrawDragFeedback
        '''     from ShapeDesigner.  This draws reversible feedback by
        '''     drawing a reversible line.
        '''
        Friend Overrides Sub DrawDragFeedback(ByVal hitObject As Object, ByVal backColor As Color, ByVal screenOffset As Point, ByVal mouseOffset As Point)
            Dim startPoint As New Point(Line.StartPoint.X, Line.StartPoint.Y)
            Dim endPoint As New Point(Line.EndPoint.X, Line.EndPoint.Y)
            startPoint.Offset(screenOffset.X, screenOffset.Y)
            endPoint.Offset(screenOffset.X, screenOffset.Y)

            Dim draw As Boolean = False

            If hitObject Is HitStart Or hitObject Is HitMove Then
                startPoint.Offset(mouseOffset.X, mouseOffset.Y)
                draw = True
            End If

            If hitObject Is HitEnd Or hitObject Is HitMove Then
                endPoint.Offset(mouseOffset.X, mouseOffset.Y)
                draw = True
            End If

            ' There is a very subtle but planned behavior here that should be
            ' noted.  Our designer supports multiple selection.  It is impossible
            ' to select two different types of shapes and do anything other than
            ' move them, however, because they all have different rules.  So, if
            ' an ellipse was selected and resized, the hitObject passed to us won't
            ' be one we understand.  In this case trans will be null and we won't
            ' move our shape.
            '
            If draw Then
                ControlPaint.DrawReversibleLine(startPoint, endPoint, backColor)
            End If
        End Sub
    End Class
End Namespace
